/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
define(["require", "exports", "../utils/convert", "./completionItemProvider"], function (require, exports, convert_1, completionItemProvider_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    const configurationNamespace = 'jsDocCompletion';
    var Configuration;
    (function (Configuration) {
        Configuration.enabled = 'enabled';
    })(Configuration || (Configuration = {}));
    class JsDocCompletionItem extends completionItemProvider_1.CompletionItem {
        constructor(document, position, shouldGetJSDocFromTSServer, vscode) {
            super('/** */', vscode, vscode.CompletionItemKind.Snippet);
            this.detail = 'JSDoc comment';
            this.insertText = '';
            this.sortText = '\0';
            const line = document.lineAt(position.line).text;
            const prefix = line.slice(0, position.character).match(/\/\**\s*$/);
            const suffix = line.slice(position.character).match(/^\s*\**\//);
            const start = position.translate(0, prefix ? -prefix[0].length : 0);
            this.range = new this.vscode.Range(start, position.translate(0, suffix ? suffix[0].length : 0));
            this.command = {
                title: 'Try Complete JSDoc',
                command: TryCompleteJsDocCommand.COMMAND_NAME,
                arguments: [document.uri, start, shouldGetJSDocFromTSServer]
            };
        }
    }
    class JsDocCompletionProvider {
        constructor(client, commandManager, vscode) {
            this.client = client;
            this.vscode = vscode;
            commandManager.register(new TryCompleteJsDocCommand(client, vscode));
        }
        provideCompletionItems(document, position, token) {
            return __awaiter(this, void 0, void 0, function* () {
                const file = this.client.normalizePath(document.uri);
                if (!file) {
                    return [];
                }
                var vscode = this.vscode;
                // TODO: unregister provider when disabled
                const enableJsDocCompletions = this.vscode.workspace.getConfiguration(configurationNamespace, document.uri).get(Configuration.enabled, true);
                if (!enableJsDocCompletions) {
                    return [];
                }
                // Only show the JSdoc completion when the everything before the cursor is whitespace
                // or could be the opening of a comment
                const line = document.lineAt(position.line).text;
                const prefix = line.slice(0, position.character);
                if (prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) === null) {
                    return [];
                }
                const args = {
                    file
                };
                const response = yield Promise.race([
                    this.client.execute('navtree', args, token),
                    new Promise((resolve) => setTimeout(resolve, 250))
                ]);
                if (!response || !response.body) {
                    return [];
                }
                const body = response.body;
                function matchesPosition(tree) {
                    if (!tree.spans.length) {
                        return false;
                    }
                    const span = convert_1.tsTextSpanToVsRange(tree.spans[0], vscode);
                    if (position.line === span.start.line - 1 || position.line === span.start.line) {
                        return true;
                    }
                    return tree.childItems ? tree.childItems.some(matchesPosition) : false;
                }
                if (!matchesPosition(body)) {
                    return [];
                }
                return [new JsDocCompletionItem(document, position, enableJsDocCompletions, this.vscode)];
            });
        }
        resolveCompletionItem(item, _token) {
            return item;
        }
    }
    exports.default = JsDocCompletionProvider;
    class TryCompleteJsDocCommand {
        constructor(client, vscode) {
            this.client = client;
            this.vscode = vscode;
            this.id = TryCompleteJsDocCommand.COMMAND_NAME;
        }
        /**
         * Try to insert a jsdoc comment, using a template provide by typescript
         * if possible, otherwise falling back to a default comment format.
         */
        execute(resource, start, shouldGetJSDocFromTSServer) {
            return __awaiter(this, void 0, void 0, function* () {
                const file = this.client.normalizePath(resource);
                if (!file) {
                    return false;
                }
                const editor = this.vscode.window.activeTextEditor;
                if (!editor || editor.document.uri.fsPath !== resource.fsPath) {
                    return false;
                }
                if (!shouldGetJSDocFromTSServer) {
                    return this.tryInsertDefaultDoc(editor, start);
                }
                const didInsertFromTemplate = yield this.tryInsertJsDocFromTemplate(editor, file, start);
                if (didInsertFromTemplate) {
                    return true;
                }
                return this.tryInsertDefaultDoc(editor, start);
            });
        }
        tryInsertJsDocFromTemplate(editor, file, position) {
            return __awaiter(this, void 0, void 0, function* () {
                const snippet = yield TryCompleteJsDocCommand.getSnippetTemplate(this.client, file, position, this.vscode);
                if (!snippet) {
                    return false;
                }
                return editor.insertSnippet(snippet, position, { undoStopBefore: false, undoStopAfter: true });
            });
        }
        static getSnippetTemplate(client, file, position, vscode) {
            const args = convert_1.vsPositionToTsFileLocation(file, position);
            return Promise.race([
                client.execute('docCommentTemplate', args),
                new Promise((_, reject) => setTimeout(reject, 250))
            ]).then((res) => {
                if (!res || !res.body) {
                    return undefined;
                }
                return TryCompleteJsDocCommand.templateToSnippet(res.body.newText, vscode);
            }, () => undefined);
        }
        static templateToSnippet(template, vscode) {
            // TODO: use append placeholder
            let snippetIndex = 1;
            template = template.replace(/^\s*(?=(\/|[ ]\*))/gm, '');
            template = template.replace(/^(\/\*\*\s*\*[ ]*)$/m, (x) => x + `\$0`);
            template = template.replace(/\* @param([ ]\{\S+\})?\s+(\S+)\s*$/gm, (_param, type, post) => {
                let out = '* @param ';
                if (type === ' {any}' || type === ' {*}') {
                    out += `{\$\{${snippetIndex++}:*\}} `;
                }
                else if (type) {
                    out += type + ' ';
                }
                out += post + ` \${${snippetIndex++}}`;
                return out;
            });
            return new vscode.SnippetString(template);
        }
        /**
         * Insert the default JSDoc
         */
        tryInsertDefaultDoc(editor, position) {
            const snippet = new this.vscode.SnippetString(`/**\n * $0\n */`);
            return editor.insertSnippet(snippet, position, { undoStopBefore: false, undoStopAfter: true });
        }
    }
    TryCompleteJsDocCommand.COMMAND_NAME = '_typeScript.tryCompleteJsDoc';
});
